You are provided with a dataset of images of plant seedlings at various stages of grown. Each image has a filename that is its unique id. The dataset comprises 12 plant species. The goal of the project is to create a classifier capable of determining a plant's species from a photo.
The dataset can be download from Olympus. The data file names are:
The original files are from Kaggle.
Due to the large volume of data, the images were converted to images.npy file and the labels are also put into the Labels.csv.
So that you can work on the data/project seamlessly without worrying about the high data volume.
Can you differentiate a weed from a crop seedling?
The ability to do so effectively can mean better crop yields and better stewardship of the environment.
The Aarhus University Signal Processing group, in collaboration with University of Southern Denmark, has recently released a dataset containing images of unique plants belonging to 12 species at several growth stages
#Libraries for plotting graphs
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
import numpy as np
import pandas as pd
from tensorflow.keras.utils import to_categorical
from sklearn.preprocessing import OneHotEncoder
from sklearn.model_selection import train_test_split
import cv2
from tensorflow.keras.models import Sequential # Sequential groups a linear stack of layers into a Keras Model
from tensorflow.keras.layers import Conv2D # Conv2D creates a convolution kernel that is convolved with the layer input to produce a tensor of outputs.
from tensorflow.keras.layers import MaxPooling2D # Max pooling operation for 2D spatial data.
from tensorflow.keras.layers import Flatten # Flattens the input. Does not affect the batch size.
from tensorflow.keras.layers import Dense, Dropout # Dropout: Applies Dropout to the input.
# Dense: Just a regular densely-connected NN layer.
# Libraries for classification model evaluation
from sklearn.metrics import confusion_matrix, recall_score, precision_score, f1_score, roc_curve,roc_auc_score,accuracy_score
images = np.load('images.npy') # load images from images.npy file
labels = pd.read_csv('labels.csv') # load lables from labels.csv file
print(images.shape) # display the shape of images vector
Insights
print(labels.shape) # display the shape of labels df
print(labels.dtypes)
labels.head() # display head of labels dataframe
# shape of single image
print(images[0].shape)
Insights
print(images[0]) # display the vector value for one image
Insights
# As display 4750 images is computationally expensive task, I am displaying only first 15 images.
i=0
for img in images[:16]:
print(f'Label : {labels.iloc[i].values}')
i = i+1
plt.imshow(img)
plt.show()
a. Normalization. b. Gaussian Blurring. c. Visualize data after pre-processing.
#Implementing GaussianBlur on each image
images_blur = []
i = 0
for img in images:
t = cv2.GaussianBlur(img, (5, 5), 4)
images_blur.append(t)
# Display first few images after implementing GaussianBlur
i=0
for img in images_blur[:16]:
print(f'Label : {labels.iloc[i].values}') # print corresponding label
i = i+1
plt.imshow(img) # display image
plt.show()
# Normalize the values
images_blur = np.array(images_blur)
images_blur_norm = images_blur.astype('float32')
images_blur_norm /= 255
# Display images after Normalization
i = 0
for img in images_blur_norm[:16]:
print(f'Label : {labels.iloc[i].values}')
i = i+1
plt.imshow(img)
plt.show()
# Display number of unique values in the lables
labels.Label.nunique()
# get the unique value names in the given labels.
labels.Label.unique()
Insights
There are 12 unique labels in the given list.
import pandas as pd
y_pdf = labels
y_pdf = pd.get_dummies(y_pdf)
print(y_pdf.shape)
y_pdf.head()
y = np.array(y_pdf) # y labels are converted to vectors.
print(y[0]) # display first vector
print(y.shape) # display shape of the vector
(Hint: First split images and labels into training and testing set with test_size = 0.3. Then further split test data into test and validation set with test_size = 0.5)
# Split data into training set and test set
Img_train, Img_test, lbl_train, lbl_test = train_test_split(images_blur_norm, y, test_size = 0.3, random_state = 7)
#Split the train set into training set and validation set
Img_val, Img_test, lbl_val ,lbl_test = train_test_split(Img_test, lbl_test, test_size=0.5, random_state=1)
# Display the shape of training , test and validation data sets
print( "Shape of Img_train: ", Img_train.shape)
print( "Shape of lbl_train: ", lbl_train.shape)
print( "Shape of Img_val: ", Img_val.shape)
print( "Shape of lbl_val: ", lbl_val.shape)
print( "Shape of Img_test: ", Img_test.shape)
print( "Shape of lbl_test: ", lbl_test.shape)
Insights on shape of training, validation and test sets
Training data set.
Validation data set.
Test data set.
# Code to display Confusion Matrix
def make_confusion_matrix(cf,
group_names=None,
categories='auto',
count=True,
percent=True,
cbar=True,
xyticks=True,
xyplotlabels=True,
sum_stats=True,
figsize=None,
cmap='Blues',
title=None):
'''
This function will make a pretty plot of an sklearn Confusion Matrix cm using a Seaborn heatmap visualization.
Arguments
'''
#( accuracy, precision, recall, f1_score ) = (0,0,0,0)
# CODE TO GENERATE TEXT INSIDE EACH SQUARE
blanks = ['' for i in range(cf.size)]
if group_names and len(group_names)==cf.size:
group_labels = ["{}\n".format(value) for value in group_names]
else:
group_labels = blanks
if count:
group_counts = ["{0:0.0f}\n".format(value) for value in cf.flatten()]
else:
group_counts = blanks
if percent:
group_percentages = ["{0:.2%}".format(value) for value in cf.flatten()/np.sum(cf)]
else:
group_percentages = blanks
box_labels = [f"{v1}{v2}{v3}".strip() for v1, v2, v3 in zip(group_labels,group_counts,group_percentages)]
box_labels = np.asarray(box_labels).reshape(cf.shape[0],cf.shape[1])
# CODE TO GENERATE SUMMARY STATISTICS & TEXT FOR SUMMARY STATS
if sum_stats:
#Accuracy is sum of diagonal divided by total observations
accuracy = np.trace(cf) / float(np.sum(cf))
#if it is a binary confusion matrix, show some more stats
if len(cf)==2:
#Metrics for Binary Confusion Matrices
precision = cf[1,1] / sum(cf[:,1])
recall = cf[1,1] / sum(cf[1,:])
f1_score = 2*precision*recall / (precision + recall)
stats_text = "\n\nAccuracy={:0.3f}\nPrecision={:0.3f}\nRecall={:0.3f}\nF1 Score={:0.3f}".format(
accuracy,precision,recall,f1_score)
else:
stats_text = "\n\nAccuracy={:0.3f}".format(accuracy)
else:
stats_text = ""
# SET FIGURE PARAMETERS ACCORDING TO OTHER ARGUMENTS
if figsize==None:
#Get default figure size if not set
figsize = plt.rcParams.get('figure.figsize')
if xyticks==False:
#Do not show categories if xyticks is False
categories=False
# MAKE THE HEATMAP VISUALIZATION
plt.figure(figsize=figsize)
sns.heatmap(cf,annot=box_labels,fmt="",cmap=cmap,cbar=cbar,xticklabels=categories,yticklabels=categories)
if xyplotlabels:
plt.ylabel('True label')
plt.xlabel('Predicted label' + stats_text)
else:
plt.xlabel(stats_text)
if title:
plt.title(title)
# Initialising the CNN classifier
classifier1 = Sequential()
# Add a Convolution layer with 64 kernels of 3X3 shape with activation function ReLU
classifier1.add(Conv2D(64, (3, 3), input_shape = (128, 128, 3), activation = 'relu', padding = 'same'))
# Add another Convolution layer with 64 kernels of 3X3 shape with activation function ReLU
classifier1.add(Conv2D(64, (3, 3), activation = 'relu', padding = 'same'))
# Add another Convolution layer with 32 kernels of 3X3 shape with activation function ReLU
classifier1.add(Conv2D(32, (3, 3), activation = 'relu', padding = 'same'))
# Adding another pooling layer
classifier1.add(MaxPooling2D(pool_size = (2, 2)))
# Flattening the layer before fully connected layers
classifier1.add(Flatten())
# Adding a fully connected layer with 512 neurons
classifier1.add(Dense(units = 512, activation = 'relu'))
# Adding a fully connected layer with 128 neurons
classifier1.add(Dense(units = 128, activation = 'relu'))
# The final output layer with 12 neurons to predict the categorical classifcation
classifier1.add(Dense(units = 12, activation = 'softmax'))
classifier1.summary()
# Compile the model
classifier1.compile(loss="categorical_crossentropy", metrics=["accuracy"], optimizer="adam")
# Fit the model
history1 = classifier1.fit(x=Img_train, y=lbl_train, batch_size=32, epochs=10, validation_data=(Img_val, lbl_val))
# Capturing accuracy per epoch
hist1 = pd.DataFrame(history1.history)
hist1['epoch'] = history1.epoch
# Plotting Accuracy at different epochs
plt.plot(hist1['accuracy'])
plt.plot(hist1['val_accuracy'])
plt.legend(("train" , "valid") , loc =0)
Insights from Accuracy plot
All these indicate over fit of the model.
# Capturing loss per epoch
hist1 = pd.DataFrame(history1.history)
hist1['epoch'] = history1.epoch
# Plotting loss at different epochs
plt.plot(hist1['loss'])
plt.plot(hist1['val_loss'])
plt.legend(("train" , "valid") , loc = 0)
Insights from Loss plot
All these indicate over fit of the model.
import seaborn as sn
y_pred1 = classifier1.predict(Img_test) # get probability
# Convert prediction probabilites to predictions
y_pred2 = np.zeros(shape=(y_pred1.shape[0],12))
for i in range(len(y_pred1)):
max_ind = np.argmax(y_pred1[i])
for j in range(12):
if(j == max_ind):
y_pred2[i][j] = int(1)
else:
y_pred2[i][j] = int(0)
y_pred2 = y_pred2.astype(int) # precition matrix
print(y_pred1.shape)
print(range(len(y_pred2)))
cm1 = confusion_matrix(lbl_test.argmax(axis=1), y_pred2.argmax(axis=1))
labels = ['True Negative','False Positive','False Negative','True Positive']
categories = ['Small-flowered Cranesbill', 'Fat Hen', 'Shepherds Purse',
'Common wheat', 'Common Chickweed', 'Charlock', 'Cleavers',
'Scentless Mayweed', 'Sugar beet', 'Maize', 'Black-grass',
'Loose Silky-bent']
make_confusion_matrix(cm1,
group_names=labels,
categories=categories,
cmap='Blues', figsize =(10,10))
Insights from Confusion Matrix
score = classifier1.evaluate(Img_test, lbl_test)
Insights from Classfier1
# Initialising the CNN classifier
classifier2 = Sequential()
# Add a Convolution layer with 128 kernels of 3X3 shape with activation function ReLU
classifier2.add(Conv2D(128, (3, 3), input_shape = (128, 128, 3), activation = 'relu', padding = 'same'))
# Add another Convolution layer with 64 kernels of 3X3 shape with activation function ReLU
classifier2.add(Conv2D(64, (3, 3), activation = 'relu', padding = 'same'))
# Add another Convolution layer with 64 kernels of 3X3 shape with activation function ReLU
classifier2.add(Conv2D(64, (3, 3), activation = 'relu', padding = 'valid'))
# Add another Convolution layer with 32 kernels of 3X3 shape with activation function ReLU
classifier2.add(Conv2D(32, (3, 3), activation = 'relu', padding = 'valid'))
# Adding another pooling layer
classifier2.add(MaxPooling2D(pool_size = (2, 2)))
# Flattening the layer before fully connected layers
classifier2.add(Flatten())
# Adding a fully connected layer with 512 neurons
classifier2.add(Dense(units = 512, activation = 'relu'))
# Adding a fully connected layer with 128 neurons
classifier2.add(Dense(units = 128, activation = 'relu'))
# The final output layer with 12 neurons to predict the categorical classifcation
classifier2.add(Dense(units = 12, activation = 'softmax'))
classifier2.summary()
import tensorflow.keras.callbacks
# Compile the model
classifier2.compile(loss="categorical_crossentropy", metrics=["accuracy"], optimizer="adam")
# Fit the model
history2 = classifier2.fit(x=Img_train, y=lbl_train, batch_size=32, epochs=10, validation_data=(Img_val, lbl_val))
# Capturing recall per epoch
hist2 = pd.DataFrame(history2.history)
hist2['epoch'] = history2.epoch
# Plotting recall at different epochs
plt.plot(hist2['accuracy'])
plt.plot(hist2['val_accuracy'])
plt.legend(("train" , "valid") , loc =0)
Insights from Accuracy plot
All these indicate over fit of this model(Classifier2).
Validation accuracy has reduced compared to Classifier1 model. Adding one exter conv2D layer to Classifier2 has increased overfit on model. This should be handled.
# Capturing learning history per epoch
hist2 = pd.DataFrame(history2.history)
hist2['epoch'] = history2.epoch
# Plotting accuracy at different epochs
plt.plot(hist2['loss'])
plt.plot(hist2['val_loss'])
plt.legend(("train" , "valid") , loc = 0)
Insights from Loss plot
All these indicate over fit of the model.
loss is more than classifier1. loss can be reduced further.
import seaborn as sn
y_pred1 = classifier2.predict(Img_test) # Prediction probability matrix
print(y_pred1.shape)
print(range(len(y_pred2)))
# Logic to convert prediction probabilities to predictions
y_pred2 = np.zeros(shape=(y_pred1.shape[0],12))
for i in range(len(y_pred1)):
max_ind = np.argmax(y_pred1[i])
for j in range(12):
if(j == max_ind):
y_pred2[i][j] = int(1)
else:
y_pred2[i][j] = int(0)
y_pred2 = y_pred2.astype(int) # Prection matrix
cm2 = confusion_matrix(lbl_test.argmax(axis=1), y_pred2.argmax(axis=1))
labels = ['True Negative','False Positive','False Negative','True Positive']
categories = ['Small-flowered Cranesbill', 'Fat Hen', 'Shepherds Purse',
'Common wheat', 'Common Chickweed', 'Charlock', 'Cleavers',
'Scentless Mayweed', 'Sugar beet', 'Maize', 'Black-grass',
'Loose Silky-bent']
make_confusion_matrix(cm2,
group_names=labels,
categories=categories,
cmap='Blues', figsize =(10,10))
Insights from Confusion Matrix
score2 = classifier2.evaluate(Img_test, lbl_test)
Insights from Classfier2
# Initialising the CNN classifier
classifier3 = Sequential()
# Add a Convolution layer with 32 kernels of 3X3 shape with activation function ReLU
classifier3.add(Conv2D(64, (3, 3), input_shape = (128, 128, 3), activation = 'relu', padding = 'same'))
# Add a Max Pooling layer of size 2X2
classifier3.add(MaxPooling2D(pool_size = (2, 2)))
# Add another Convolution layer with 32 kernels of 3X3 shape with activation function ReLU
classifier3.add(Conv2D(64, (3, 3), activation = 'relu', padding = 'same'))
# Add a Max Pooling layer of size 2X2
classifier3.add(MaxPooling2D(pool_size = (2, 2)))
# Add another Convolution layer with 32 kernels of 3X3 shape with activation function ReLU
classifier3.add(Conv2D(32, (3, 3), activation = 'relu', padding = 'valid'))
# Adding another pooling layer
classifier3.add(MaxPooling2D(pool_size = (2, 2)))
# Flattening the layer before fully connected layers
classifier3.add(Flatten())
# Adding a fully connected layer with 512 neurons
classifier3.add(Dense(units = 512, activation = 'relu'))
# Adding dropout with probability 0.5
classifier3.add(Dropout(0.5))
# Adding a fully connected layer with 128 neurons
classifier3.add(Dense(units = 128, activation = 'relu'))
# The final output layer with 12 neurons to predict the categorical classifcation
classifier3.add(Dense(units = 12, activation = 'softmax'))
classifier3.summary()
# Compile the model
classifier3.compile(loss="categorical_crossentropy", metrics=["accuracy"], optimizer="adam")
# Fit the model
history3 = classifier3.fit(x=Img_train, y=lbl_train, batch_size=32, epochs=10, validation_data=(Img_val, lbl_val))
# Capturing recall per epoch
hist3 = pd.DataFrame(history3.history)
hist3['epoch'] = history3.epoch
# Plotting recall at different epochs
plt.plot(hist3['accuracy'])
plt.plot(hist3['val_accuracy'])
plt.legend(("train" , "valid") , loc =0)
Insights from Accuracy plot
Overall this model shows improvement in the prediction capability.
# Capturing recall per epoch
hist3 = pd.DataFrame(history3.history)
hist3['epoch'] = history3.epoch
# Plotting recall at different epochs
plt.plot(hist3['loss'])
plt.plot(hist3['val_loss'])
plt.legend(("train" , "valid") , loc =0)
Insights from Loss plot
The over fit has reduced.
## Model3 ##
import seaborn as sn
y_pred1 = classifier3.predict(Img_test) # prediction probability matrix
print(y_pred1.shape)
print(range(len(y_pred2)))
# Logic to convert prediction probabilities to predictions
y_pred2 = np.zeros(shape=(y_pred1.shape[0],12))
for i in range(len(y_pred1)):
max_ind = np.argmax(y_pred1[i])
for j in range(12):
if(j == max_ind):
y_pred2[i][j] = int(1)
else:
y_pred2[i][j] = int(0)
y_pred2 = y_pred2.astype(int) # get prediction matrix
cm3 = confusion_matrix(lbl_test.argmax(axis=1), y_pred2.argmax(axis=1))
labels = ['True Negative','False Positive','False Negative','True Positive']
categories = ['Small-flowered Cranesbill', 'Fat Hen', 'Shepherds Purse',
'Common wheat', 'Common Chickweed', 'Charlock', 'Cleavers',
'Scentless Mayweed', 'Sugar beet', 'Maize', 'Black-grass',
'Loose Silky-bent']
make_confusion_matrix(cm3,
group_names=labels,
categories=categories,
cmap='Blues', figsize =(10,10))
Insights from Confusion Matrix
score3 = classifier3.evaluate(Img_test, lbl_test)
classifier3.summary()
Conv2D:
Keras Conv2D is a 2D Convolution Layer, this layer creates a convolution kernel which helps produce a tensor of outputs.
Here for this model (Classifier3), I have declared 3 Conv2D layers with 128,64, 32 nuerons. Added Maxpooling in between
Activation('relu'):
ReLu function is used as Activation function for all layers except output layer where softmax function is used for multiclass classification.
'relu' stands for Rectified linear unit. It is the most widely used activation function. Chiefly implemented in hidden layers of Neural network.
MaxPooling2D:
Dropout:
Dense Layers:
Softmax:
classifier3.evaluate(Img_test, lbl_test)
Insights
## Model3 ##
import seaborn as sn
y_pred1 = classifier3.predict(Img_test) # prediction probability matrix
print(y_pred1.shape)
print(range(len(y_pred2)))
# Logic to convert prediction probabilities to predictions
y_pred2 = np.zeros(shape=(y_pred1.shape[0],12))
for i in range(len(y_pred1)):
max_ind = np.argmax(y_pred1[i])
for j in range(12):
if(j == max_ind):
y_pred2[i][j] = int(1)
else:
y_pred2[i][j] = int(0)
y_pred2 = y_pred2.astype(int) # get prediction matrix
cm3 = confusion_matrix(lbl_test.argmax(axis=1), y_pred2.argmax(axis=1))
labels = ['True Negative','False Positive','False Negative','True Positive']
categories = ['Small-flowered Cranesbill', 'Fat Hen', 'Shepherds Purse',
'Common wheat', 'Common Chickweed', 'Charlock', 'Cleavers',
'Scentless Mayweed', 'Sugar beet', 'Maize', 'Black-grass',
'Loose Silky-bent']
make_confusion_matrix(cm3,
group_names=labels,
categories=categories,
cmap='Blues', figsize =(10,10))
Insights from Confusion Matrix
x_test[2], x_test[3], x_test[33], x_test[36], x_test[59]
#Verify x_test[2]
plt.imshow(Img_test[2])
plt.show()
print("Actual data ", lbl_test[2])
print("Prediction data",y_pred2[2])
print("Given Label is ",categories[y_pred2[2].argmax()])
# Prediction label and Actual label is same. Correct Prediction
#Verify x_test[3]
plt.imshow(Img_test[3])
plt.show()
print("Actual data ", lbl_test[3])
print("Prediction data",y_pred2[3])
print("Given Label is ",categories[y_pred2[3].argmax()])
# Prediction label and Actual label is same. Correct Prediction
#Verify x_test[33]
plt.imshow(Img_test[33])
plt.show()
print("Actual data ", lbl_test[33])
print("Prediction data",y_pred2[33])
print("Given Label is ",categories[y_pred2[33].argmax()])
# Prediction label and Actual label is same. Correct Prediction
#Verify x_test[36]
plt.imshow(Img_test[36])
plt.show()
print("Actual data ", lbl_test[36])
print("Prediction data",y_pred2[36])
print("Given Label is ",categories[y_pred2[36].argmax()])
# Prediction label and Actual label is same. Correct Prediction
#Verify x_test[59]
plt.imshow(Img_test[59])
plt.show()
print("Actual data ", lbl_test[59])
print("Prediction data",y_pred2[59])
print("Given Label is ",categories[y_pred2[59].argmax()])
# Prediction label and Actual label is same. Correct Prediction
In this case study deep CNNs in Keras is used for image classification.
In data given there are 12 variety of plant photos with labels. Need to identify the names correctly using this model.
From the prediction results and confusion matrix, we can get below key takeaways
Common Wheat, Sugar beet, Black grass are top three categories which show high prediction rate. which means these trees look distinct and they can be easily identified.
Cleavers is mostly wrongly predicted as 'Small-flowered Cranesbill'. May be these two plants look similar.
Common Chickweed,Maize, Scentless Mayweed showed very less Accuracy rate as These plants are few in the given dataset. also they showed less prediction accuracy.
THe numbers show that the weeds like(Common Chickweed,Scentless Mayweed) are mostly predicted correctly. The results will help to differenciate between weeds and seed lings.